package gbench.sandbox.matrix;

import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.*;
import java.lang.reflect.Array;
import static gbench.sandbox.matrix.JunitMyMatrix.Tuple2.*;

/**
 * 
 * @author gbench
 *
 */
public class JunitMyMatrix {
    
    /**
     * 二元组
     * @author gbench
     *
     * @param <T> 第一元素类型
     * @param <U> 第二元素类型
     */
    public static class Tuple2<T,U>{
        /**
         * 构造函数
         * @param t 第一元素
         * @param u 第二元素
         */
        public Tuple2(final T t, final U u){
            this._1 = t;
            this._2 = u;
        }
        
        /**
         * 第一元素
         * @return
         */
        public T _1() {return _1;}
        
        /**
         * 第二元素
         * @return
         */
        public U _2() {return _2;}
        
        private final T _1;// 第一元素
        private final U _2;// 第二元素
        
        /**
         * 
         * @param <T>
         * @param <U>
         * @param t
         * @param u
         * @return
         */
        public static <T,U>  Tuple2<T,U> TUP2(T t,U u){
            return new Tuple2<>(t,u);
        }
        
        /**
         * 
         * @return
         */
        public static Stream<Integer> st(){
           return Stream.iterate(0,i->i+1);
        }

        /**
         * 
         * @param n
         * @return
         */
        public static Stream<Integer> st(int n){
            return Stream.iterate(0,i->i+1).limit(n);
        }
        
        /**
         * 拉链函数
         * @param <T>
         * @param <U>
         * @param tt
         * @param uu
         * @return 拉链函数
         */
        public static <T,U> Stream<Tuple2<T,U>> zip(final T[] tt, final U[] uu){
            final var tsize = tt.length;
            final var usize = uu.length;
            return st(Math.max(tsize,usize)).map(i->TUP2(tt[i%tsize],uu[i%usize]));
        }
    }
    
    

    /**
     * 提取数据矩阵的 形状 (高度,宽度)
     * @param <T>
     * @param tt 数据军阵
     * @return (高度,宽度) 二元组
     */
    public static <T> Tuple2<Integer,Integer> shape(T[][] tt){
        return TUP2(tt.length,Stream.of(tt).filter(e->e!=null).collect(Collectors.summarizingInt(e->e.length)).getMax());
    }

    /**
     * 快速生成数组的方法。
     * @param <T>
     * @param tt
     * @return 数组对象
     */
    @SafeVarargs
    public static <T> T[] A(T ... tt){
        return tt;
    }
    
    /**
     * 生成数据列表
     * @param <T>
     * @param tt
     * @return 数据列表
     */
    @SafeVarargs
    public static <T> List<T> L(T...tt){
        return Arrays.asList(tt);
    }
    
    
    
    /**
     * 
     * @param <T>
     * @param <U>
     * @param aa
     * @param bb
     * @return
     */
    public static <T extends Number,U extends Number> Double[] add(T[] aa,U[] bb){
        return zip(aa,bb).map(e->e._1.doubleValue()+e._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * 
     * @param <T>
     * @param <U>
     * @param aa
     * @param bb
     * @return
     */
    public static <T extends Number,U extends Number> Double[] sub(T[] aa,U[] bb){
        return zip(aa,bb).map(e->e._1.doubleValue()-e._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * 
     * @param <T>
     * @param <U>
     * @param aa
     * @param bb
     * @return
     */
    public static <T extends Number,U extends Number> Double[] mul(T[] aa,U[] bb){
        return zip(aa,bb).map(e->e._1.doubleValue()*e._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * 
     * @param <T>
     * @param <U>
     * @param aa
     * @param b
     * @return
     */
    public static <T extends Number,U extends Number> Double[] mul(T[] aa,U b){
        return zip(aa,A(b)).map(e->e._1.doubleValue()*e._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * 
     * @param <T>
     * @param <U>
     * @param aa
     * @param bb
     * @return
     */
    public static <T extends Number,U extends Number> Double[] div(T[] aa,U[] bb){
        return zip(aa,bb).map(e->e._1.doubleValue()/e._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * 向量点积
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 向量点积
     */
    public static <T extends Number,U extends Number> Double dot(T[] tt,U[] uu) {
        return zip(tt,uu).map(e->e._1.doubleValue()*e._2.doubleValue()).reduce((a,b)->a+b).orElse(tt[0].doubleValue()*uu[0].doubleValue());
    }

    /**
     * 矩阵转置
     * @param <T> 元素类型
     * @param tt 矩阵数据
     * @return 矩阵转置
     */
    @SuppressWarnings("unchecked")
    public static <T> T[][] transpose(T[][] tt){
        final var shape = shape(tt);
        final var t1class = tt[0][0].getClass();
        final var t2class = tt[0].getClass();
        return st(shape._2)
                .map(i->st(shape._1)
                    .map(j->tt[j][i])
                    .toArray(n->(T[]) Array.newInstance(t1class,n)))
                .toArray(m->(T[][])Array.newInstance(t2class,m));
    }
    
    /**
     * 矩阵乘法
     * @param <T>
     * @param <U>
     * @param tt 左乘矩阵
     * @param uu 右乘矩阵
     * @return 矩阵乘法结果
     */
    public static <T extends Number,U extends Number> Double[][] mmult(final T[][] tt,final U[][] uu){
        final var ss = transpose(uu);
        final var shape = TUP2(shape(tt)._1,shape(uu)._2);
        return st(shape._1)
            .map(i->st(shape._2)
                .map(j->dot(tt[i],ss[j]))
                .toArray(Double[]::new))
            .toArray(Double[][]::new);
    }
    
    /**
     * 对一个向量集合 做施密特正交
     * @param <T> 向量元素类型
     * @param alphas
     * @return 施密特的正交向量集合
     */
    public static <T extends Number> List<Double[]> schmidt(final List<T[]> alphas){
        if(alphas==null)return null;
        final var betas = new LinkedList<Double[]>();
        final var eps = 1e-10;
        final Predicate<Double[]> is_zero = aa->Stream.of(aa).allMatch(e->Math.abs(e)<eps);
        alphas.forEach(alpha->{
            final var t = betas.stream()
                    .map(beta->mul(beta,dot(alpha,beta)/dot(beta,beta)))
                    .reduce(JunitMyMatrix::add).map(ita->sub(alpha,ita))
                    .orElse(Stream.of(alpha).map(e->e.doubleValue()).toArray(Double[]::new));
            if(!is_zero.test(t))betas.add(t);
        });
        return betas;
    }

    /**
     * 矩阵格式化输出
     * @param <T>
     * @param mm 数据矩阵
     * @return 格式化数据局
     */
    public static <T> String fmt(T[][] mm){
        return Stream.of(mm)
            .map(m->Stream.of(m)
                .map(e->e+"")
                .collect(Collectors.joining("\t")))
            .collect(Collectors.joining("\n"));
    }

    /**
     * 程序演示
     */
    @Test
    public void foo(){
        var aa = A(1,2,3,4);
        var bb = A(aa,aa);
        var cc = transpose(bb);
        System.out.println(fmt(cc));
        
        final var dd = schmidt(L(aa,aa));
        dd.forEach(d->{
            System.out.println(Arrays.asList(d));
        });
    }

}
